#include "FrameBufferHandler.h"
#include <gl/glut.h>
#include "ObjHandler.h"
#include <malloc.h>
#include <glui.h>
#include "ShaderHandler.h"
#include "TextureHandler.h"
#include "MatixInvert.h"

#pragma comment(lib,"glui32.lib");

GLUI *glui;

OBJHandler mesh;

int *displayID;

int windowWidth=1024, windowHeight=768;
int diffuseWidth, diffuseHeight;

float obj_rot[16];
float lig_rot[16];

GLfloat light_position[4]={1.0, 1.0, 1.0, 0.0};

GLfloat eye_position[4]={0,0,35,1};

GLfloat light_distance=30;

int KdC[3]={8,8,8};
int KsC=7;
int KshineC=8;

GLfloat Kd[3]={0.8,0.8,0.8};
GLfloat Ks[3]={0.7,0.7,0.7};
GLfloat Kshine=0.8;

CGcontext context;
CGprofile vProfile, fProfile;

GLuint bufferID;

int albedoID;
int chanelR;
int chanelB;
int chanelG;
int chanelS;

GLfloat ProjectMatrix[16];
GLfloat MVMatrix[16];
GLfloat MInvMatrix[16];
GLfloat MTranMatrix[16];

ShaderHandler vDiffuse, fDiffuse, vSpecular, fSpecular,vRender,fRender;
FrameBufferHandler DiffuseBuffer, SpecularBuffer;

int *texture;

void cgErrorCallback(void)
{
	CGerror lastError = cgGetError();
	if(lastError) {
		const char *listing = cgGetLastListing(context);
		printf("%s\n", cgGetErrorString(lastError));
		printf("%s\n", listing);
		exit(-1);
	}
}

void initFrameBuffer()
{
	glewInit();

	glGenFramebuffersEXT(1,&bufferID);

	DiffuseBuffer.SetBufferID(bufferID);
	DiffuseBuffer.SetUpFBO(diffuseWidth,diffuseHeight);
	SpecularBuffer.SetBufferID(bufferID);
	SpecularBuffer.SetUpFBO(diffuseWidth,diffuseHeight);
}

void loadTexture()
{
	texture=new int[8];

	TextureHandler albedoHandler;
	albedoHandler.Load("diff.bmp");
	texture[0]=albedoHandler.ogl_id;
	diffuseWidth=albedoHandler.img_width;
	diffuseHeight=albedoHandler.img_height;

	TextureHandler chanelRHandler;
	chanelRHandler.Load("diff_normal_r.bmp");
	texture[1]=chanelRHandler.ogl_id;

	TextureHandler chanelGHandler;
	chanelGHandler.Load("diff_normal_g.bmp");
	texture[3]=chanelGHandler.ogl_id;

	TextureHandler chanelBHandler;
	chanelBHandler.Load("diff_normal_b.bmp");
	texture[2]=chanelBHandler.ogl_id;

	TextureHandler chanelSHandler;
	chanelSHandler.Load("spec.bmp");
	texture[4]=chanelSHandler.ogl_id;

	albedoID=texture[0];
	chanelR=texture[1];
	chanelB=texture[2];
	chanelG=texture[3];
	chanelS=texture[4];
}

void iniShader()
{
	vDiffuse.m_parameters.clear();

	fDiffuse.m_parameters.clear();
	fDiffuse.AddParameter("albedo",CG_GL_TEXTURE_2D,&albedoID);
	fDiffuse.AddParameter("chanelR",CG_GL_TEXTURE_2D,&chanelR);
	fDiffuse.AddParameter("chanelG",CG_GL_TEXTURE_2D,&chanelG);
	fDiffuse.AddParameter("chanelB",CG_GL_TEXTURE_2D,&chanelB);
	fDiffuse.AddParameter("MInverse",CG_GL_ARBITRARY_MTX,MInvMatrix);
	fDiffuse.AddParameter("light",CG_GL_VECTOR_4D,light_position);
	fDiffuse.AddParameter("eye",CG_GL_VECTOR_4D,eye_position);
	fDiffuse.AddParameter("Kd",CG_GL_VECTOR_3D,Kd);

	vSpecular.m_parameters.clear();

	fSpecular.m_parameters.clear();
	fSpecular.AddParameter("albedo",CG_GL_TEXTURE_2D,&albedoID);
	fSpecular.AddParameter("chanelS",CG_GL_TEXTURE_2D,&chanelS);
	fSpecular.AddParameter("MInverse",CG_GL_ARBITRARY_MTX,MInvMatrix);
	fSpecular.AddParameter("light",CG_GL_VECTOR_4D,light_position);
	fSpecular.AddParameter("eye",CG_GL_VECTOR_4D,eye_position);
	fSpecular.AddParameter("Ks",CG_GL_VECTOR_3D,Ks);
	fSpecular.AddParameter("Kshine",CG_GL_VECTOR_1D,&Kshine);

	vRender.m_parameters.clear();
	vRender.AddParameter("Proj",CG_GL_ARBITRARY_MTX,ProjectMatrix);
	vRender.AddParameter("MV",CG_GL_ARBITRARY_MTX,MVMatrix);

	fRender.m_parameters.clear();
	fRender.AddParameter("chanelD",CG_GL_TEXTURE_2D,&(DiffuseBuffer.framebuffer.texID));
	fRender.AddParameter("chanelS",CG_GL_TEXTURE_2D,&(SpecularBuffer.framebuffer.texID));
}

void loadShader()
{

	vDiffuse.m_context=context;
	vDiffuse.Load("vDiffuse.cg",CG_GL_VERTEX);

	fDiffuse.m_context=context;
	fDiffuse.Load("fDiffuse.cg",CG_GL_FRAGMENT);

	vSpecular.m_context=context;
	vSpecular.Load("vSpecular.cg",CG_GL_VERTEX);

	fSpecular.m_context=context;
	fSpecular.Load("fSpecular.cg",CG_GL_FRAGMENT);

	vRender.m_context=context;
	vRender.Load("vRender.cg",CG_GL_VERTEX);

	fRender.m_context=context;
	fRender.Load("fRender.cg",CG_GL_FRAGMENT);
}

void initCg()
{
	cgSetErrorCallback(cgErrorCallback);
	context=cgCreateContext();
}

void init()
{
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);

}
void timer(int p)
{
	glutPostRedisplay();
	glutTimerFunc(20, timer, 0);
}

void updateCgParam()
{
	for (int i=0;i<3;i++)
	{
		Kd[i]=(float)KdC[i]/10.0;
		Ks[i]=(float)KsC/10.0;
	}
	Kshine=(float)KshineC/10.0;
}

void updateMatrix()
{

	float length=0;

	for (int i=0;i<3;i++)
	{
		light_position[i]=0;
		for (int j=0;j<3;j++)
		{
			light_position[i]+=lig_rot[j*4+i];
		}
		length+=light_position[i]*light_position[i];
	}	

	length=sqrt(length);

	for (int i=0;i<3;i++)
	{
		light_position[i]/=length;
		light_position[i]*=light_distance;
	}

	glViewport(0, 0, (GLsizei) diffuseWidth, (GLsizei) diffuseHeight);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, diffuseWidth, 0.0, diffuseHeight);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(eye_position[0],eye_position[1],eye_position[2],0,0,0 ,0,1,0);
	glMultMatrixf(obj_rot);

	glGetFloatv(GL_PROJECTION_MATRIX,ProjectMatrix);
	glGetFloatv(GL_MODELVIEW_MATRIX,MVMatrix);	
	transposeMatrix(MTranMatrix,obj_rot);
	float invTemp[16];
	invertMatrix(invTemp,MTranMatrix);
	transposeMatrix(MInvMatrix,invTemp);
	//invertMatrix(MInvMatrix,obj_rot);
}

void drawModel()
{
	glCallList(displayID[0]);
}

void display()
{	

	updateCgParam();

	// Diffuse Pass

	DiffuseBuffer.FBOActivate();

	updateMatrix();

	vDiffuse.bind();
	fDiffuse.bind();

	drawModel();

	fDiffuse.release();
	vDiffuse.release();

	DiffuseBuffer.FBODeactivate();

	// Specular Pass                                                        

	SpecularBuffer.FBOActivate();

	updateMatrix();

	vSpecular.bind();
	fSpecular.bind();

	drawModel();

	fSpecular.release();
	vSpecular.release();

	SpecularBuffer.FBODeactivate();

	// Final Combination                                                    

	glClearColor(1,1,1,1);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f,(GLfloat) windowWidth / (GLfloat)windowHeight , 0.5f, -1000.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glViewport(0, 0, windowWidth, windowHeight);

	gluLookAt(eye_position[0],eye_position[1],eye_position[2],0,0,0 ,0,1,0);
	glMultMatrixf(obj_rot);

	glGetFloatv(GL_PROJECTION_MATRIX,ProjectMatrix);
	glGetFloatv(GL_MODELVIEW_MATRIX,MVMatrix);	

	vRender.bind();
	fRender.bind();

	glCallList(displayID[0]);

	fRender.release();
	vRender.release();

	glutSwapBuffers(); 

}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f,(GLfloat) w / (GLfloat)h , 0.5f, -1000.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void InitModel()
{
	displayID = (int*)malloc(sizeof(int));

	
	mesh.LoadOBJascii("lowres.obj");

	displayID[0] = glGenLists(1);
	glNewList(displayID[0], GL_COMPILE);
	glBegin(GL_TRIANGLES);
	for(int j=0; j<mesh.numTris; j++){
		int indexV1 = mesh.tris[j].v1;
		int indexV2 = mesh.tris[j].v2;
		int indexV3 = mesh.tris[j].v3;
		int indexUV1 = mesh.tris[j].uv1;
		int indexUV2 = mesh.tris[j].uv2;
		int indexUV3 = mesh.tris[j].uv3;
		glTexCoord2f(mesh.uvs[indexUV1].u, mesh.uvs[indexUV1].v); 
		glNormal3f(mesh.normal[indexV1].x, mesh.normal[indexV1].y, mesh.normal[indexV1].z);
		glColor3f(mesh.tangent[indexV1].x, mesh.tangent[indexV1].y, mesh.tangent[indexV1].z);
		glVertex3f(mesh.verts[indexV1].x, mesh.verts[indexV1].y, mesh.verts[indexV1].z);

		glTexCoord2f(mesh.uvs[indexUV2].u, mesh.uvs[indexUV2].v); 
		glNormal3f(mesh.normal[indexV2].x, mesh.normal[indexV2].y, mesh.normal[indexV2].z);
		glColor3f(mesh.tangent[indexV2].x, mesh.tangent[indexV2].y, mesh.tangent[indexV2].z);
		glVertex3f(mesh.verts[indexV2].x, mesh.verts[indexV2].y, mesh.verts[indexV2].z);

		glTexCoord2f(mesh.uvs[indexUV3].u, mesh.uvs[indexUV3].v); 
		glNormal3f(mesh.normal[indexV3].x, mesh.normal[indexV3].y, mesh.normal[indexV3].z);
		glColor3f(mesh.tangent[indexV3].x, mesh.tangent[indexV3].y, mesh.tangent[indexV3].z);
		glVertex3f(mesh.verts[indexV3].x, mesh.verts[indexV3].y, mesh.verts[indexV3].z);
	}
	glEnd();
	glEndList();
}

int main(int argc, char *argv[])
{

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowPosition(0, 0);
	glutInitWindowSize(windowWidth, windowHeight);
	GLuint win=glutCreateWindow("Hybrid Normal");
	init();
	initCg();
	InitModel();
	loadTexture();
	initFrameBuffer();
	loadShader();
	iniShader();
	glutTimerFunc(20,timer,0);
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);

	glui=GLUI_Master.create_glui("Control");

	GLUI_Rollout *Rotation=glui->add_rollout("Rotation");
	GLUI_Rotation *ro=glui->add_rotation_to_panel(Rotation,"World Rotation",(float*)obj_rot);
	ro->reset();
	GLUI_Rotation *rl=glui->add_rotation_to_panel(Rotation,"Light Rotation",(float*)lig_rot);
	rl->reset();

	GLUI_Rollout *Translation=glui->add_rollout("Translation");
	GLUI_Translation *tc=glui->add_translation_to_panel(Translation,"Camera Translation",GLUI_TRANSLATION_Z,&(eye_position[2]));
	tc->set_speed(0.5);
	tc->set_z(eye_position[2]);
	GLUI_Translation *tl=glui->add_translation_to_panel(Translation,"Light Translation",GLUI_TRANSLATION_Z,&(light_distance));
	tl->set_speed(0.5);
	tl->set_z(light_distance);

	GLUI_Rollout *DiffuseLightControl=glui->add_rollout("Diffuse Lighting");
	GLUI_Spinner *kdr=glui->add_spinner_to_panel(DiffuseLightControl,"Diffuse R Kd",GLUI_SPINNER_INT,&(KdC[0]));
	kdr->set_int_limits(0,10);
	GLUI_Spinner *kdg=glui->add_spinner_to_panel(DiffuseLightControl,"Diffuse G Kd",GLUI_SPINNER_INT,&(KdC[1]));
	kdg->set_int_limits(0,10);
	GLUI_Spinner *kdb=glui->add_spinner_to_panel(DiffuseLightControl,"Diffuse B Kd",GLUI_SPINNER_INT,&(KdC[2]));
	kdb->set_int_limits(0,10);

	GLUI_Rollout *SpecularLightControl=glui->add_rollout("Specular Lighting");
	GLUI_Spinner *kds=glui->add_spinner_to_panel(SpecularLightControl,"Specular Ks",GLUI_SPINNER_INT,&KsC);
	kds->set_int_limits(0,10);
	GLUI_Spinner *ksh=glui->add_spinner_to_panel(SpecularLightControl,"Specular Shine",GLUI_SPINNER_INT,&KshineC);
	ksh->set_int_limits(0,100);

	glui->set_main_gfx_window(win);

	glutMainLoop();
	return 0;
}